import bat.*
import static bat.Builder.*
import static bat.FileUtil.*
import static bat.ProcUtil.*
import static bat.ZipBuilder.*
import static bat.ZipExtractor.*


/**
 * The build is encapsulated in an instance so that we can use method abstractions
 * (not just closures) and not type the keyword static.
 */
class Main extends Builder
{
	final String projectName = "s-expressions"
	final int versionMajor = 1
	final int versionMinor = 0
	
	final String fullName = projectName + "-" + versionMajor + "." + versionMinor
	final String distName = fullName + ".zip"
	
	// define all the directories we might care about in the build.
	
	final File rootDir = makePath (".")
	final File rsrcDir = makePath (rootDir, "resources")
	final File sourceDir = makePath (rootDir, "source")
	final File sourceMainDir = makePath (sourceDir, "main")
	final File sourceUnitDir = makePath (sourceDir, "unit")
	final File binDir = makePath (rootDir, "bin")
	final File buildDir = makePath (rootDir, "build")
	final File libsDir = makePath (buildDir, "libs")
	final File libsMainDir = makePath (libsDir, "main")
	final File libsUnitDir = makePath (libsDir, "unit")
	// the extra level ensures we play nice with IDEs, other build systems, etc.
	final File targetDir = makePath (rootDir, ["build", "bat"])
	final File classesDir = makePath (targetDir, "classes")
	final File classesMainDir = makePath (classesDir, "main")
	final File classesUnitDir = makePath (classesDir, "unit")
	
	final String rsrcScala = "scala-2.9.1.final.zip"
	final String rsrcScalaTest = "scalatest-1.6.1.zip"

	final String libScala = "scala-library.jar"
	final String libScalaTest = "scalatest-1.6.1.jar"
	
	// Handy file filters.
	final def notSVN = nand (isDir, name ("\\.svn"))
	final def scalaSources = and (notSVN, or (isDir, ext ("scala")))
	
	File resourceFile (String name) { makePath (rsrcDir, name) }
	// download the resource if it's not there.
	void updateResource (String name, String url)
	{
		File rsrc = resourceFile (name)
		if (! rsrc.exists ())
			downloadFile (rsrc, url)
		else Log.verbose { "Resource up-to-date: " + name }
	}
	private Main ()
	{
		// A banner is always nice.
		println ("---\n--- Project: " + fullName + "\n---")
		Log.setVerbose ()
		
		// Set up the targets
		
		addTarget ("resources", "downloads resources",
			["clean" : option (null)])
		{ opts ->
			if (opts.exists ("clean"))
				deleteDir (rsrcDir)
			createDir (rsrcDir)
			updateResource (rsrcScala, "http://www.scala-lang.org/downloads/distrib/files/" + rsrcScala)
			updateResource (rsrcScalaTest, "http://www.artima.com/downloadScalaTest/" + rsrcScalaTest)
		}
		addTarget ("libs", "extracts libs from resources",
			["clean" : option (null)])
		{ opts ->
			if (opts.exists ("clean"))
				deleteDir (libsDir)
			createDir (libsDir)
			createDir (libsMainDir)
			createDir (libsUnitDir)
			unzipByTarget (resourceFile (rsrcScala), 
				[ unzipFile ("scala-2.9.1.final/lib/" + libScala, makePath (libsMainDir, libScala))])
			unzipByTarget (resourceFile (rsrcScalaTest), 
				[ unzipFile ("scalatest-1.6.1/" + libScalaTest, makePath (libsUnitDir, libScalaTest))])
		}
		addTarget ("build", "compile and jar the code",
			["clean" : option (null)])
		{ opts ->
			String jarName = projectName + ".jar"
			File jarFile = makePath (targetDir, jarName)
			if (opts.exists ("clean"))
			{
				refreshDir (classesDir)
				deleteFile (jarFile)
			}
			def srcMain = collectFiles (sourceMainDir, scalaSources)
			Check.cond (0 < srcMain.size ()) { "No source files in " + srcMain }
			def srcUnit = collectFiles (sourceUnitDir, scalaSources)
			Check.cond (0 < srcUnit.size ()) { "No source files in " + srcUnit }
			// println ("deps " + checkDependencies ([jarFile], srcMain))
			boolean rebuildMain = 
				! classesMainDir.exists () || 
				! checkDependencies ([jarFile], srcMain)
			boolean rebuildUnit = rebuildMain || 
				! classesUnitDir.exists () || 
				! checkDependencies (srcMain, srcUnit)
			def libsMain = collectFiles (libsMainDir, ext ("jar"))
			// compile main
			print ("--- compile: main: ")
			if (rebuildMain)
			{
				println (srcMain.size () + " source file(s).")
				refreshDir (classesMainDir)
				// delete this so that a failed build doesn't leave an old jar behind.
				deleteFile (jarFile)
				scalac (false, false, libsMain, sourceMainDir, createDir (classesMainDir), srcMain)
			}
			else println ("up-to-date")
			// compile unit
			print ("--- compile: unit: ")
			if (rebuildUnit)
			{
				println (srcUnit.size () + " source file(s).")
				refreshDir (classesUnitDir)
				// The unit cp includes the main classes and libs.
				def libsUnit = collectFiles (libsUnitDir, ext ("jar"))
				libsUnit.add (classesMainDir)
				libsUnit.addAll (libsMain)
				scalac (false, false, libsUnit, sourceUnitDir, createDir (classesUnitDir), srcUnit)
			}
			else println ("up-to-date")
			if (! jarFile.exists ())
			{
				zipToFile (jarFile)
				{ ZipBuilder zip ->
					listFiles (classesMainDir, any) { f -> zip.insertFileRelative ("", classesMainDir, f) }
				}
			}
		}
		addTarget ("dist", "zip project into distribution file", 
			null)
		{ opts ->
			File zipFile = makePath (distName)
			deleteFile (zipFile)
			zipToFile (zipFile)
			{ ZipBuilder zip ->
				def here = makePath (".")
				[ ".classpath", ".project", "build.groovy" ].each { 
					name -> zip.insertFileFlatten ("", new File (name)) }
				listFiles (sourceDir, notSVN) { 
					file -> zip.insertFileRelative ("", here, file) }
				zip.insertFileFlatten ("", makePath (targetDir, projectName + ".jar"))
			}
		}
	}
	static void main (String [] args)
	{
		new Main ().runBuild (args)
	}
}

